/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFTOFWall
  \brief TOF Wall class. Just a container for holding hits
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-29
  \date 2021-11-24 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-29   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <cstring>
#include <TClonesArray.h>
#include <TGraph.h>
#include "FairRootManager.h"
#include "ETFTOFWall.h"
#include "ETFDetectorManager.h"
#include "ETFPlaHit.h"
#include "ETFMath.h"
#include "ETFMsg.h"
#include "ETFHPTDCChannel.h"
#include "ETFTrack.h"
#include "ETFParaManager.h"
#include "ETFYaml.h"

using std::cout;
using std::endl;

ClassImp(ETFTOFWall);

static const double DEGREE = ETFMath::DEGREE();
ETFTOFWall::ETFTOFWall(const char *name) : ETFDetector(name), WIDTH(41.67){
  NS = 30;

  // NOTE that the chMap generating rule is upon how specifically the daq is arranged
  ETFParaManager *pm = ETFParaManager::Instance();
  const YAML::Node &chIdConfig = (*pm->GetChIdPara())[name];
  for(int i = 0; i < NS; i++){
    if(chIdConfig[i]){ // j: [0-1-2-3]: [UV-UH-DV-DH]
      for(int j = 4; j--;) if(chIdConfig[i].as<int>() >= 0)
        SetChIdMap(chIdConfig[i].as<int>()+j, fDetId + (i<<8) + (j<<13));
    } // end if
    else ETFMsg::Warn("ETFTOFWall", "ctor: chId of strip#%d not found in yaml config file", i);
  } // end for over strips
  const YAML::Node &delayConfig = (*pm->GetDelayPara())["tofwstr"];
  for(int i = 0; i < NS; i++) fDelayStr[i] = delayConfig[i].as<double>();
  // pass user trig bound parameters //
  const YAML::Node &trigBConfig = (*pm->GetMisPara())["tofwTrigB"];
  if(trigBConfig){
    fTrigLB = trigBConfig[0].as<double>();
    fTrigHB = trigBConfig[1].as<double>();
  } // end if

  fHits = new TClonesArray("ETFPlaHit");
  fHits->SetName(name);
} // end ctor

ETFTOFWall::~ETFTOFWall(){
  if(fHits) delete fHits;
} // end the dtor

void ETFTOFWall::Register(){
  static FairRootManager *m = FairRootManager::Instance();
  if(fHits) m->Register(fHits->GetName(), "DetHits", fHits, true);
} // end member function Register

// initialize the detector
void ETFTOFWall::Initialize(){
  ETFDetector::Initialize();
  fHits->Clear();
  fStripIdMap.clear();
} // end member function Initialize

// number of valid fired strips
int ETFTOFWall::GetNFiredStrip() const{
  int cnt = 0;
  for(auto p : *fHits)
    if(4 == dynamic_cast<ETFPlaHit *>(p)->GetFiredStatus()) cnt++;
  return cnt;
} // end member function GetNFiredStrip

// get fired strip by serial id
ETFPlaHit *ETFTOFWall::GetStrip(int id){
  if(!fStripIdMap.count(id))
    ETFMsg::Error("ETFTOFWall", "GetStrip: required strip id %d not found", id);
  return dynamic_cast<ETFPlaHit *>(fHits->At(fStripIdMap[id]));
} // end member function GetStrip

// add the data channel to the detector
void ETFTOFWall::Assign(const ETFHPTDCChannel *ch){
  int uid = ETFDetectorManager::Instance()->GetUID(ch->GetChannelId());
  if(fDetId != (uid & 0xFF)) ETFMsg::Error("ETFTOFWall", "Assign: not from this detector.");
  int stripId = uid>>8 & 0x1F;
  int udvh = uid>>13 & 0x3;
  AddHit(ch, stripId, udvh);
} // end member function Assign

// add strip to fGDumb
void ETFTOFWall::ConstructGraph(){
  if(fGDumb->GetN()) return; // has called

  fGDumb->SetMarkerStyle(28);
  fGDumb->SetMarkerColor(30); // gray
  for(int i = 0; i < NS; i++){
    const double x0 = WIDTH*((1.-NS)/2.+i);
    double p[3] = {cos(fAng[0])*x0, 0., 0.};
    TransformToLabRef(p);
    fGDumb->SetPoint(i, p[2], p[0]);
  } // end for over i
} // end member function ConstructGraph

void ETFTOFWall::AddHit(const ETFHPTDCChannel *ch, int stripId, int udvh){
  ETFPlaHit *hit = nullptr;
  for(auto p : *fHits){
    ETFPlaHit *h = dynamic_cast<ETFPlaHit *>(p);
    if(h->GetStripId() == stripId){ // old strip
      hit = h; continue;
    } // end if
  } // end for
  if(!hit){ // need to create a new strip
    fStripIdMap[stripId] = fHits->GetEntries();
    hit = dynamic_cast<ETFPlaHit *>(fHits->ConstructedAt(fHits->GetEntries()));
    hit->Initialize();
    hit->SetStripId(stripId);
    // assign strip position
    const double x0 = WIDTH*((1.-NS)/2.+stripId);
    hit->SetX(cos(fAng[0])*x0);
    hit->SetY(0.);
    hit->SetZ(0.);
    hit->SetDelay(fDelay + fDelayStr[stripId]);
    char name[128]; sprintf(name, "%s_StripId%d", GetName(), stripId);
    hit->SetName(name);

    TransformToLabRef(hit);
  } // end if
  hit->SetDataChannel(ch, udvh);
} // end member function AddHit

bool ETFTOFWall::AssignDriftTimeStart(ETFTrack *trk){
  double nstripStrayMin = 1.e200, id = -1;
  for(auto p : *fHits){
    ETFPlaHit *h = dynamic_cast<ETFPlaHit *>(p);
    if(h->GetFiredStatus() != 4) continue;
    double z, x; h->GetZX(z, x);
    const double nstripStray = trk->DistanceToLine(z, x);
    if(fabs(nstripStray) < fabs(nstripStrayMin)){
      nstripStrayMin = nstripStray;
      id = h->GetStripId();
    } // end if
  } // end for over TOF Wall strip hits
  // TODO: readin user parameters
  nstripStrayMin /= WIDTH; // in strip width
  // not close enough to any strip
  // TODO: expected user input configuration here
  if(!ETFMath::Within(nstripStrayMin, -0.7, 0.7) || -1 == id) return false;
  trk->SetFiredStripId(id);
  trk->SetNStripStray(nstripStrayMin);
  trk->SetDriftTimeStart(GetStrip(id)->GetTime());
  return true;
} // end member function AssignDriftTimeStart

void ETFTOFWall::DrawHits(){
  for(const auto &p : *fHits) if(p){
    ETFPlaHit *h = dynamic_cast<ETFPlaHit *>(p);
    if(h->GetFiredStatus() == 4) h->Draw();
  } // end for and if
} // end member function DrawHits

void ETFTOFWall::Print(){
  ETFDetector::Print();
  cout << "Strip Width: " << WIDTH << endl;
  cout << "Number of Fired Strips: " << GetNFiredStrip() << endl;
  for(auto p : *fHits) dynamic_cast<ETFPlaHit *>(p)->Print();
} // end member function Print
